package evq.codec.core.managers.impl;

import java.util.ArrayList;
import java.util.List;

import evq.codec.core.entities.Dictionary;
import evq.codec.core.entities.EncodeMethod;
import evq.codec.core.entities.PgmQuanta;
import evq.codec.core.entities.Profiler;
import evq.codec.core.entities.Quanta;
import evq.codec.core.entities.Quantum;
import evq.codec.core.entities.Vector;
import evq.codec.core.managers.VqManager;

public class DefaultVqManager implements VqManager {

	private static DefaultVqManager instance = new  DefaultVqManager();
	
	private DefaultVqManager(){
		
	}
	
	public static DefaultVqManager getInstance(){
		return instance;
	}
	
	public Quanta quantize(List<Vector> vectors, Dictionary dictionary,
			QuantizeType quantizeType, EncodeMethod encodeMethod,
			Profiler profiler) {
		
		Quanta quanta = null;
		
		if(quantizeType == QuantizeType.DEFAULT){
			quanta = new Quanta();
		}else{
			quanta = new PgmQuanta();
		}
		
		if (profiler != null) {
			profiler.startProfiling();
		}
		
		switch (encodeMethod) {
		case FS:
			fullSearch(vectors, dictionary, quanta, profiler);
			break;
		case PDS:
			partialDistanceSearch(vectors, dictionary, quanta, profiler);
			break;
		}
		
		if (profiler != null) {
			profiler.stopProfiling();
		}
		
		return quanta;
	}

	public List<Vector> unquantize(Quanta quanta, Dictionary dictionary) {
		
		//Initializing the vectors that will be returned.
		List<Vector> vectors = new ArrayList<Vector>();
		
		//Iterating over the list of quanta to get the respective vectors of the dictionary.
		for (Quantum quantum : quanta) {
		
			//Getting the code of the vector in the dictionary.
			int dictionaryCode = quantum.getValue();
			
			//Getting the vector of the dictionary in the specified position.
			Vector dictionaryVector = null;
			try {
				dictionaryVector = (Vector) dictionary.getVector(dictionaryCode).clone();
			} catch (CloneNotSupportedException e) {
				e.printStackTrace();
			}
			
			//Adding the vector of the dictionary in the returned list of vectors.
			vectors.add(dictionaryVector);
		}
		
		return vectors;
	}
	
	private void fullSearch(List<Vector> vectors, Dictionary dictionary,Quanta quanta,Profiler profiler){
		
		//Iterating over the vectors of the image.
		for(Vector imageVector : vectors){
			
			//Initializing the smaller distance to distance between of the current image vector and the first vector of the dictionary.
			double smallerDistance = calculateDistance(imageVector, dictionary.getVector(0), profiler);
			profiler.decrementSumOperations();
			
			//Initializing the position of the dictionary to first position.
			int positionOfTheDictionary = 0;
			
			//Iterating over the vectors of the dictionary to find the vector of it that have the smaller distance of the imageVector
			for(int i = 1; i < dictionary.getSize(); i++){
				profiler.decrementSumOperations();
				//Getting the distance between the image vector and the current vector of the dictionary. 
				double distance = calculateDistance(imageVector, dictionary.getVector(i), profiler);
				
				//Comparing the current distance with the smaller distance to update the position of the dictionary.
				if(distance < smallerDistance){
					smallerDistance = distance;
					positionOfTheDictionary = i;
				}
				
				//Incrementing the Operations.
				if(profiler != null){
					profiler.incrementComparisonOperations();
				}
			}
			//Creating the quantum that represents the current image vector.
			Quantum quantum = new Quantum(positionOfTheDictionary);
			
			//Adding the quantum to the list of quanta.
			quanta.addQuantum(quantum);
		}
	}
	
	private void partialDistanceSearch(List<Vector> vectors, Dictionary dictionary,Quanta quanta,Profiler profiler) {
		//Iterating over the vectors of the image.
		for(Vector imageVector : vectors){
			
			//Initializing the smaller distance to distance between of the current image vector and the first vector of the dictionary.
			double smallerDistance = calculateDistance(imageVector, dictionary.getVector(0), profiler);
			profiler.decrementSumOperations();
			
			//Initializing the position of the dictionary to first position.
			int positionOfTheDictionary = 0;
			
			//Iterating over the vectors of the dictionary to find the vector of it that have the smaller distance of the imageVector
			for(int i = 1; i < dictionary.getSize(); i++){
				profiler.decrementSumOperations();
				//Getting the distance between the image vector and the current vector of the dictionary. 
				double distance = calculateDistance(imageVector, dictionary.getVector(i), profiler, smallerDistance);
				
				//Comparing the current distance with the smaller distance to update the position of the dictionary.
				if(distance < smallerDistance){
					smallerDistance = distance;
					positionOfTheDictionary = i;
				}
				
				//Incrementing the Operations.
				if(profiler != null){
					profiler.incrementComparisonOperations();
				}
			}
			//Creating the quantum that represents the current image vector.
			Quantum quantum = new Quantum(positionOfTheDictionary);
			
			//Adding the quantum to the list of quanta.
			quanta.addQuantum(quantum);
		}
	}
	
	private double calculateDistance(Vector vector, Vector dictionaryVector,Profiler profiler){
		
		//Initializing distance to zero
		double distance = 0;
		
		//Iterating over the vector and the dictionary passed by parameters to calculate the distance 
		for(int i = 0; i < vector.size(); i++){
			
			//Calculating the distance between the element of the vector and the respective element of the dictionary
			double temp = vector.getElement(i) - dictionaryVector.getElement(i);
			//Calculating the quadratic of the calculated distance
			distance += Math.pow(temp, 2);
			
			//Incrementing the operations.
			if(profiler != null){
				profiler.incrementSubtractOperations();
				profiler.incrementSumOperations();
				profiler.incrementMultiplicationOperations();
			}
		}
		
		return distance;
	}
	
	private double calculateDistance(Vector vector, Vector dictionaryVector,Profiler profiler, double smallerDistance){
		
		//Initializing distance to zero
		double distance = 0;
		
		//Iterating over the vector and the dictionary passed by parameters to calculate the distance 
		for(int i = 0; i < vector.size(); i++){
			
			//Calculating the distance between the element of the vector and the respective element of the dictionary
			double temp = vector.getElement(i) - dictionaryVector.getElement(i);
			//Calculating the quadratic of the calculated distance
			distance += Math.pow(temp, 2);
			
			//Incrementing the operations.
			if(profiler != null){
				profiler.incrementSubtractOperations();
				profiler.incrementSumOperations();
				profiler.incrementMultiplicationOperations();
			}

			profiler.incrementComparisonOperations();
			if (distance > smallerDistance) {
				return smallerDistance;
			}
		}
		
		return distance;
	}
}
